﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEditor;
using UnityEngine;
using CSharpExtensions;

namespace CSharpExtensions
{
    public static class TypeExtensions
    {
        public static string ToGenericTypeString(this Type t)
        {
            if (!t.IsGenericType)
                return t.Name;
            string genericTypeName = t.GetGenericTypeDefinition().Name;
            genericTypeName = genericTypeName.Substring(0,
                genericTypeName.IndexOf('`'));
            string genericArgs = string.Join(",",
                                     t.GetGenericArguments()
                .Select(ta => ToGenericTypeString(ta)).ToArray());
            return genericTypeName + "<" + genericArgs + ">";
        }
    }
}

public static class MonoScriptDumper
{

    static Type[] BaseTypes =
    {
        typeof(bool),
        typeof(byte),
        typeof(sbyte),
        typeof(short),
        typeof(ushort),
        typeof(int),
        typeof(uint),
        typeof(long),
        typeof(ulong),
        typeof(float),
        typeof(double),
        typeof(decimal),
        typeof(string),
    };

    static Dictionary<Type, String> typeAlias = new Dictionary<Type, String>();
    static HashSet<Type> BaseTypeSet = new HashSet<Type>();
    static Dictionary<Type, String> dumpCache = new Dictionary<Type, String>();
    static object locker = new object();

    static MonoScriptDumper()
    {
        for (var i = 0; i < BaseTypes.Length; ++i)
        {
            BaseTypeSet.Add(BaseTypes[i]);
        }
        typeAlias[typeof(bool)] = "bool";
        typeAlias[typeof(byte)] = "byte";
        typeAlias[typeof(sbyte)] = "sbyte";
        typeAlias[typeof(byte)] = "byte";
        typeAlias[typeof(short)] = "short";
        typeAlias[typeof(ushort)] = "ushort";
        typeAlias[typeof(int)] = "int";
        typeAlias[typeof(uint)] = "uint";
        typeAlias[typeof(long)] = "long";
        typeAlias[typeof(ulong)] = "ulong";
        typeAlias[typeof(float)] = "float";
        typeAlias[typeof(double)] = "double";
        typeAlias[typeof(decimal)] = "decimal";
        typeAlias[typeof(string)] = "string";
    }

    static String GetAlias(Type t)
    {
        String o;
        if (typeAlias.TryGetValue(t, out o))
        {
            return o;
        }
        if (t.Namespace == "UnityEngine")
        {
            return t.Name;
        }
        return t.FullName;
    }

    public static bool DumpType(Type ftype, bool ignoreSerializableAttribute, IDictionary<string, List<KeyValuePair<string, string>>> typeInfoMap)
    {
        if (typeInfoMap.ContainsKey(ftype.FullName))
        {
            return true;
        }

        if (typeof(Delegate).IsAssignableFrom(ftype))
        {
            return false;
        }

        if (BaseTypeSet.Contains(ftype) ||
            ftype.IsEnum)
        {
            if (!typeInfoMap.ContainsKey(ftype.FullName))
            {
                typeInfoMap.Add(ftype.FullName, null);
            }
            return true;
        }
        else if (ftype.IsArray)
        {
            var et = ftype.GetElementType();
            if (et != null)
            {
                if (!typeInfoMap.ContainsKey(ftype.FullName))
                {
                    if (DumpType(et, ignoreSerializableAttribute, typeInfoMap))
                    {
						if(!typeInfoMap.ContainsKey(ftype.FullName))
                        {
							List<KeyValuePair<string, string>> fields = new List<KeyValuePair<string, string>>();
							fields.Add(new KeyValuePair<string, string>("element", et.FullName));
							typeInfoMap.Add(ftype.FullName, fields);
						}
                    }
                }
            }
        }
        else if (ftype.IsGenericType)
        {
            var listType = typeof(List<>);
            var gargs = ftype.GetGenericArguments();
            if (gargs.Length == 1)
            {
                if (ftype == listType.MakeGenericType(gargs))
                {
                    if (!typeInfoMap.ContainsKey(ftype.FullName))
                    {
                        if (DumpType(gargs[0], ignoreSerializableAttribute, typeInfoMap))
                        {
							if(!typeInfoMap.ContainsKey(ftype.FullName))
							{
								List<KeyValuePair<string, string>> fields = new List<KeyValuePair<string, string>>();
								fields.Add(new KeyValuePair<string, string>("element", gargs[0].FullName));
								typeInfoMap.Add(ftype.FullName, fields);
							}
                        }
                    }
                }
            }
        }
//        else if (!isRoot && typeof(UnityEngine.Object).IsAssignableFrom(ftype))
//        {
//            // unity's object will be serialized as a pointer/GUID
//            prettyTypeName = String.Format("{0}{1} {2}\n",
//                new String('\t', indent),
//                GetAlias(ftype), name);
//            return true;
//        }
        else if (ftype.IsClass || (ftype.IsValueType && !ftype.IsEnum))
        {
            if (!ftype.IsEnum)
            {
                var attrs = System.Attribute.GetCustomAttributes(ftype);
                int nserTag = 0;
                if (!ignoreSerializableAttribute)
                {
                    nserTag = Array.FindIndex(attrs, a => a is SerializableAttribute);
                }

                if (ftype.IsClass && nserTag == -1 && !typeof(UnityEngine.Object).IsAssignableFrom(ftype))
                {
                    return false;
                }
            }
                
            List<KeyValuePair<string, string>> fieldList = new List<KeyValuePair<string, string>>();
            typeInfoMap.Add(ftype.FullName, fieldList);

            FieldInfo[] fields = ftype.GetFields(
                BindingFlags.Instance |
                BindingFlags.NonPublic |
                BindingFlags.Public |
                BindingFlags.FlattenHierarchy);
            
            if (fields != null && fields.Length > 0)
            {
                for (int i = 0; i < fields.Length; ++i)
                {
                    var fi = fields[i];
                    var _ftype = fi.FieldType;
                    var attrs = Attribute.GetCustomAttributes(fi);
                    var nserTag = Array.FindIndex(attrs, a => a is NonSerializedAttribute);
                    if (nserTag != -1)
                    {
                        continue;
                    }

                    var serTag = Array.FindIndex(attrs, a => a is SerializeField);
                    if (!(serTag != -1 || fi.IsPublic))
                    {
                        continue;
                    }
                        
                    if (DumpType(_ftype, ignoreSerializableAttribute, typeInfoMap))
                    {
                        fieldList.Add(new KeyValuePair<string, string>(fi.Name, _ftype.FullName));
                    }
                }
            }
        }

        return true;
    }

    public static void Clear()
    {
        lock (locker)
        {
            dumpCache.Clear();
        }
    }

    public static Dictionary<string, List<KeyValuePair<string, string>>> Dump(Type type)
    {
        Dictionary<string, List<KeyValuePair<string, string>>> result = new Dictionary<string, List<KeyValuePair<string, string>>>();
        DumpType(type, false, result);
        return result;
    }
}
